package play.modules.extendedValidation;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;

import javax.persistence.Entity;

import play.PlayPlugin;
import play.data.validation.Email;
import play.data.validation.Equals;
import play.data.validation.InFuture;
import play.data.validation.InPast;
import play.data.validation.Match;
import play.data.validation.Max;
import play.data.validation.MaxSize;
import play.data.validation.Min;
import play.data.validation.MinSize;
import play.data.validation.Range;
import play.data.validation.Required;
import play.data.validation.URL;
import play.modules.extendedValidation.singleton.HashMapSingleton;

/**
 * PLAY! Plugin to do some logic on startup
 * 
 * @author Ahmed
 * 
 */
public class StartUp extends PlayPlugin {

	@Override
	public void onLoad() {
		System.out.println("StartUp.onLoad()");
		ClassLoader loader = ClassLoader.getSystemClassLoader();
		java.net.URL modelsPackage = loader.getResource("models");
		String filePath = modelsPackage.getFile();
		System.out.println("FilePath OnLOAD: " + filePath);
		super.onLoad();
	}

	/**
	 * to be called from other Controllers (Testing purposes).
	 */
	public static boolean onApplicationStart2() {
		new StartUp().onApplicationStart();
		return false;
	}

	/**
	 * Called at application start (and at each reloading) Time to analyze the
	 * models and update the hashmap containing the fields and their validations
	 */
	@Override
	public void onApplicationStart() {
		System.out.println("StartUp.onApplicationStart()");
		if (HashMapSingleton.getClassFieldValidation() != null) {
			System.out.println("StartUp.onApplicationStart()" + "    --- "
					+ HashMapSingleton.getClassFieldValidation());
			return;
		}

		HashMap<String, HashMap<String, String>> classFieldValidation = new HashMap<String, HashMap<String, String>>();
		try {
			ClassLoader loader = ClassLoader.getSystemClassLoader();
			java.net.URL modelsPackage = loader.getResource("models");
			String filePath = modelsPackage.getFile();
			RandomAccessFile validationRulesFile = prepareValidationEngineRules(filePath);
			ArrayList<Class<?>> classes = getModelClasses();
			for (Class<?> c : classes) {
				HashMap<String, String> fieldsValidation = getClassFields(c,
						validationRulesFile);
				if (!fieldsValidation.isEmpty()) {
					classFieldValidation.put(c.getSimpleName(),
							fieldsValidation);
				}
				System.out.println("ForLoop: "
						+ classFieldValidation.get(c.getSimpleName()));
			}

			String jsEnd = "\n};}};$.validationEngineLanguage.newLang();})(jQuery);";
			validationRulesFile.writeBytes(jsEnd);
			validationRulesFile.getChannel().truncate(validationRulesFile.getChannel().position());
			validationRulesFile.close();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (NullPointerException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		HashMapSingleton.setClassFieldValidation(classFieldValidation);
		System.out.println("LOOOOOOL END"
				+ HashMapSingleton.getClassFieldValidation());

	}

	/**
	 * Scans all classes accessible from the {@link ContextClassLoader} which
	 * belong to the models package and annotated with {@linkplain Entity}
	 * 
	 * @return The model classes annotated with {@link Entity}
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	private static ArrayList<Class<?>> getModelClasses()
			throws ClassNotFoundException, IOException {
		ArrayList<Class<?>> modelClasses = new ArrayList<Class<?>>();
		String[] files = null;
		try {
			java.net.URL l = ClassLoader.getSystemClassLoader().getResource(
					"models");
			String s = l.getFile();
			files = new File(s).list(new FilesFilter("class"));
		} catch (Exception e) {
			e.printStackTrace();
		}
		for (String file : files) {
			Class<?> c = Class.forName("models."
					+ file.substring(0, file.indexOf(".")));
			if (c.getAnnotation(Entity.class) != null) {
				modelClasses.add(c);
			}
		}
		return modelClasses;
	}

	/**
	 * Scans the fields in the given Class {@code c} which has validation
	 * annotations {@code @play.data.validation.*} and map each field using its
	 * name as a {@code key} to its validation string which is built depending
	 * on the annotations.
	 * 
	 * @param c
	 *            the Class whose Fields will be scanned.
	 * @param validationRulesFile
	 * @return HashMap mapping each field Name as a Key to its validation
	 *         String. NOTE: The returned hashmap will not contain the fields
	 *         who are not annotated with {@code @play.data.validation.*}
	 */
	private static HashMap<String, String> getClassFields(Class<?> c,
			RandomAccessFile validationRulesFile) {
		System.out.println("StartUp.getClassFields()");
		HashMap<String, String> fieldValidation = new HashMap<String, String>();
		Field[] fields = c.getFields();
		for (Field field : fields) {
			String validationString = getFieldValidation(c.getSimpleName(),
					field, validationRulesFile);
			if (!validationString.isEmpty()) {
				fieldValidation.put(field.getName(), validationString);
			}
		}
		return fieldValidation;
	}

	/**
	 * Generates Validation for the given Field {@code field}.
	 * 
	 * @param field
	 *            is the field to generate validation for.
	 * @param validationRulesFile
	 * @return String for validation engine to validate this {@code field}.
	 */
	private static String getFieldValidation(String className, Field field,
			RandomAccessFile validationRulesFile) {
		InFuture inFuture = field.getAnnotation(InFuture.class);
		Equals equals = field.getAnnotation(Equals.class);
		InPast inPast = field.getAnnotation(InPast.class);
		Match match = field.getAnnotation(Match.class);
		Max max = field.getAnnotation(Max.class);
		MaxSize maxSize = field.getAnnotation(MaxSize.class);
		Min min = field.getAnnotation(Min.class);
		MinSize minSize = field.getAnnotation(MinSize.class);
		Required required = field.getAnnotation(Required.class);
		URL url = field.getAnnotation(URL.class);
		Email email = field.getAnnotation(Email.class);
		Range range = field.getAnnotation(Range.class);

		StringBuffer validation = new StringBuffer();

		if (inFuture != null) {
			validation.append("future[");
			if (inFuture.value().trim().isEmpty()) {
				validation.append("NOW");
			} else {
				validation.append(inFuture.value().trim());
			}
			validation.append("],");
			addErrorMessage(validation, inFuture.message());
		}// endif inFuture.

		if (equals != null && !equals.value().trim().isEmpty()) {
			validation.append(",equals[");
			validation.append(equals.value());
			validation.append("],");
			addErrorMessage(validation, equals.message());
		}// endif equals.

		if (inPast != null) {
			validation.append(",past[");
			if (inPast.value().trim().isEmpty()) {
				validation.append("NOW");
			} else {
				validation.append(inPast.value().trim());
			}
			validation.append("],");
			addErrorMessage(validation, inPast.message());
		}// endif inPast

		if (match != null && !match.value().isEmpty()) {
			addValidationRule(className, field.getName(), validationRulesFile,
					match.value(), match.message());
			validation.append(",match[");
			validation.append(className);
			validation.append(".");
			validation.append(field.getName());
			validation.append("]");
		}// endif match

		if (max != null) {
			validation.append(",max[");
			validation.append(max.value());
			validation.append("],");
			addErrorMessage(validation, max.message());
		}// endif max

		if (maxSize != null) {
			validation.append(",maxSize[");
			validation.append(maxSize.value());
			validation.append("],");
			addErrorMessage(validation, maxSize.message());
		}// endif maxSize

		if (min != null) {
			validation.append(",min[");
			validation.append(min.value());
			validation.append("],");
			addErrorMessage(validation, min.message());
		}// endif min

		if (minSize != null) {
			validation.append(",minSize[");
			validation.append(minSize.value());
			validation.append("],");
			addErrorMessage(validation, minSize.message());
		}// endif minSize

		if (required != null) {
			validation.append(",required,");
			addErrorMessage(validation, required.message());
		}// endif required

		if (url != null) {
			validation.append(",match[url],");
			addErrorMessage(validation, url.message());
		}// endif url

		if (email != null) {
			validation.append(",match[email],");
			addErrorMessage(validation, email.message());
		}// endif email

		if (range != null) {
			validation.append(",range[");
			validation.append(range.min());
			validation.append(",");
			validation.append(range.max());
			validation.append("],");
			addErrorMessage(validation, range.message());
		}// endif range
		return validation.toString();
	}// end Method getFieldValidation

	private static void addErrorMessage(StringBuffer validation, String errorMsg) {
		if (!errorMsg.trim().isEmpty()) {
			validation.append("msg:");
			validation.append(errorMsg);
		}
	}

	private static void addValidationRule(String className, String fieldName,
			RandomAccessFile validationRulesFile, String regex, String msg) {

		final String RULE_TEMPLATE = ",\n" + "                \"%s.%s\": {\n"
				+ "                    \"regex\":/%s/,\n"
				+ "                    \"alertText\":\"*%s\"" 
				+ "                \n}";
		String rule = null;
		try {
			rule = String.format(RULE_TEMPLATE, className, fieldName, regex,
					msg);
			validationRulesFile.writeBytes(rule);

		} catch (IOException e) {
			try {
				validationRulesFile.close();
			} catch (Exception e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
			System.out.println(rule);
		}
	}

	private static RandomAccessFile prepareValidationEngineRules(String filePath)
			throws Exception {
		File file = new File(filePath);

		File publicFolder;
		do {
			file = file.getParentFile();
			publicFolder = new File(file, File.separatorChar + "public");
		} while (!publicFolder.exists());

		file = new File(publicFolder, File.separatorChar + "javascripts"
				+ File.separatorChar + "jquery.validationEngine-en.js");
		System.out.println("StartUp.getClassFields()" + file.exists() + " --- "
				+ file.getAbsolutePath());

		RandomAccessFile validationRules = null;
		try {
			validationRules = new RandomAccessFile(file, "rwd");
			String line = "";
			while ((line = validationRules.readLine()) != null) {
				if (line.endsWith("}")) {
					break;
				}
			}

		} catch (IOException e) {
			try {
				validationRules.close();
			} catch (Exception e1) {
				e1.printStackTrace();
			}

			e.printStackTrace();
			return null;
		}
		return validationRules;
	}
}

class FilesFilter implements FilenameFilter {
	private String endsWith;

	public FilesFilter(String endsWith) {
		this.endsWith = endsWith;
	}

	@Override
	public boolean accept(File dir, String name) {
		return name.endsWith(endsWith);
	}
}
